/******************************************************
* drv_l2_usbd.c
*
* Purpose: usb controller L2 driver
*
* Author: Eugene Hsu
*
* Date: 2011/10/05
*
* Copyright Generalplus Corp. ALL RIGHTS RESERVED.
*
* Version :
* History :
*
*******************************************************/
/*******************************************************
    Include file
*******************************************************/
#include <string.h>
#include "drv_l1_usbd.h"
#include "drv_l2_usbd.h"
#include "drv_l2_usbd_msdc.h"

#if (_OPERATING_SYSTEM == _OS_UCOS2)
#include "os.h"
#endif

/******************************************************
    Definitions and variable declaration
*******************************************************/
//#define USBDL2DEG
#ifdef USBDL2DEG
#define USBD_L2LOG	print_string
#else
#define USBD_L2LOG(...)
#endif

/**********************************************
*	Extern variables and functions
**********************************************/
extern void							print_string(const CHAR *const fmt, ...);
#if (_OPERATING_SYSTEM == _OS_UCOS2)
//#define USBDL2TASKPRIORITY	14//9
#define USBDL2StackSize		512
#define USBD_QUEUE_MAX_LEN	64
INT32U								USBDL2Stack[USBDL2StackSize];
static void							*USBDL2Q_Stack[USBD_QUEUE_MAX_LEN];
OS_EVENT							*USBDL2Q = NULL;
static INT8U						usbdl2task; /* 1:keep usbd_l2main task alive, 0: exit while loop */
#else
extern INT32U						l1msg;		/* Check message from L1 for no OS platform */
#endif
#if (_OPERATING_SYSTEM != _OS_NONE)
static INT8U						usbdl2task; /* 1:keep usbd_l2main task alive, 0: exit while loop */
#endif
extern INT8U						Default_Device_Descriptor_TBL[];
extern INT8U						Default_Qualifier_Descriptor_TBL[];
extern INT8U						Default_Config_Descriptor_TBL[];
extern INT8U						Default_String0_Descriptor[];
extern INT8U						Default_String1_Descriptor[];
extern INT8U						Default_String2_Descriptor[];
extern INT8U						Default_String3_Descriptor[];

/*********************************************
*	Variables declaration
*********************************************/
USBD_CONTROL						ctl_ctlblk; /* Control transfer control block */

static USBD_L2_STATE_FUN			usbdl2_control_tbl[] = { NULL, /* SETUP */ NULL, /* IN */ NULL, /* OUT */ NULL };

static USBD_L2_STATE_FUN			usbdl2_bulk_tbl[] = { NULL, /* IN */ NULL, /* OUT */ NULL /* END */ };

static USBD_L2_STATE_FUN			usbdl2_iso_tbl[] = { NULL, /* IN */ NULL, /* OUT */ NULL /* END */ };

static USBD_L2_STATE_FUN			usbdl2_intr_tbl[] = { NULL, /* IN */ NULL, /* OUT */ NULL /* END */ };

static USBD_L2_STATE_FUN			usbdl2_misc_tbl[] = { NULL, /* drv_l2_usbd_misc_handle */ NULL };

/* L2 state handle table */
static USBD_L2_STATE_FUN *const		usbdl2_handle_tbl[USBD_XFER_MAX + 1] = { usbdl2_control_tbl, usbdl2_bulk_tbl, usbdl2_iso_tbl, usbdl2_intr_tbl, usbdl2_misc_tbl };

static USBD_L2_DECTECT_CALLBACK_FUN usbl2_detect_cbk = NULL;

/*****************************************************
    Local utility functions
******************************************************/
void drv_l2_usbd_l2_run_handle(INT32U msg)
{
	INT8U	type, state, event;

	USBDGetMsg(msg, type, state, event);

	USBD_L2LOG("USBDL2: drv_l2_usbd_l2main rec msg %ld, Xfer type %ld State %ld, event %d\r\n", msg, type, state, event);

	/* process state machine and event */
	if(usbdl2_handle_tbl[type] != NULL && type <= USBD_XFER_MAX)
	{
		/* Execute L2 State handle */
		if(usbdl2_handle_tbl[type][state] != NULL)
		{
			usbdl2_handle_tbl[type][state](event);
		}
	}
}

void _check_ep0_data_send_zero(void)
{
	/* Check data length is larger than wLength */
	if(ctl_ctlblk.datacnt > ctl_ctlblk.setupmsg.wLength)
	{
		ctl_ctlblk.datacnt = ctl_ctlblk.setupmsg.wLength;
	}

	if(ctl_ctlblk.datacnt && ((ctl_ctlblk.datacnt & EP0_FIFO_SIZE_MASK) == 0) && (ctl_ctlblk.setupmsg.wLength > ctl_ctlblk.datacnt))
	{
		ctl_ctlblk.ep0_sendnull = 1;
	}
}

void drv_l2_usbd_process_setup_request(void)
{
	INT32U	cnt;
	INT8U	bmtype, destype, desindex;

	//GetSetupBMReqType(ctl_ctlblk.setupmsg.bmRequestType, bmdir, bmtype, bmrecipient);
	//    bmdir = (ctl_ctlblk.setupmsg.bmRequestType & 0x80) >> 7;
	bmtype = (ctl_ctlblk.setupmsg.bmRequestType & 0x60) >> 5;

	//USBD_L2LOG("USBDL2: bmRequestType, bmdir %d, bmtype %d, bmrecipient %d\r\n", bmdir, bmtype, bmrecipient);
	//DBG_PRINT("USBDL2: SETUP data dir is %d\r\n", bmdir);
	if(bmtype == USBD_REQ_STANDARD)
	{
		/* Get descriptor standard request */
		if(ctl_ctlblk.setupmsg.bRequest == USBD_STDREQ_GET_DESCRIPTOR)
		{
			GetDescriptorbywVlaue(ctl_ctlblk.setupmsg.wValue, destype, desindex);

			//DBG_PRINT("USBDL2: USBD_STDREQ_GET_DESCRIPTOR, destype %d \r\n", destype);
			switch(destype)
			{
			case USBD_DES_DEVICE_TYPE:	/* device descriptor */
				ctl_ctlblk.dataptr = ctl_ctlblk.dev_desptr;
				ctl_ctlblk.datacnt = ctl_ctlblk.dev_desptr[0];
					/* high speed */
					/* full speed */

				break;

			case USBD_DES_CONFIG_TYPE:
			case USBD_DES_OTHER_SPEED_CONFIGURATION_TYPE:
				ctl_ctlblk.dataptr = ctl_ctlblk.config_desptr;
				ctl_ctlblk.datacnt = ctl_ctlblk.dataptr[2] | (ctl_ctlblk.dataptr[3] << 8);

				/* Normal / Other speed */
				if(destype == USBD_DES_CONFIG_TYPE)
				{
					ctl_ctlblk.config_desptr[1] = USBD_DES_CONFIG_TYPE;
					//if(!(rUDC_STATUS & MASK_USBD_UDLC_CS_CURR_SPEED))
					if(drv_l1_usbd_get_cur_speed() == USB_SPEED_20)
					{
						/* 512 bytes BULK PayLoad, high speed  */
						ctl_ctlblk.config_desptr[22] = 0x00;
						ctl_ctlblk.config_desptr[23] = 0x02;
						ctl_ctlblk.config_desptr[29] = 0x00;
						ctl_ctlblk.config_desptr[30] = 0x02;
					}
					else
					{
						/* 64 bytes BULK PayLoad */
						ctl_ctlblk.config_desptr[22] = 0x40;
						ctl_ctlblk.config_desptr[23] = 0x00;
						ctl_ctlblk.config_desptr[29] = 0x40;
						ctl_ctlblk.config_desptr[30] = 0x00;
					}
				}
				else
				if(destype == USBD_DES_OTHER_SPEED_CONFIGURATION_TYPE)
				{
					ctl_ctlblk.config_desptr[1] = USBD_DES_OTHER_SPEED_CONFIGURATION_TYPE;
					//if(!(rUDC_STATUS & MASK_USBD_UDLC_CS_CURR_SPEED))
					if(drv_l1_usbd_get_cur_speed() == USB_SPEED_20)
					{
						/* 64 bytes BULK PayLoad */
						ctl_ctlblk.config_desptr[22] = 0x40;
						ctl_ctlblk.config_desptr[23] = 0x00;
						ctl_ctlblk.config_desptr[29] = 0x40;
						ctl_ctlblk.config_desptr[30] = 0x00;
					}
					else
					{
						/* 512 bytes BULK PayLoad, high speed  */
						ctl_ctlblk.config_desptr[22] = 0x00;
						ctl_ctlblk.config_desptr[23] = 0x02;
						ctl_ctlblk.config_desptr[29] = 0x00;
						ctl_ctlblk.config_desptr[30] = 0x02;
					}
				}

				break;

			case USBD_DES_STRING_TYPE:
				switch(desindex)
				{
				case 0:
					ctl_ctlblk.dataptr = ctl_ctlblk.str0_desptr;
					break;

				case 1:
					ctl_ctlblk.dataptr = ctl_ctlblk.str1_desptr;
					break;

				case 2:
					ctl_ctlblk.dataptr = ctl_ctlblk.str2_desptr;
					break;

				case 3:
					ctl_ctlblk.dataptr = ctl_ctlblk.str3_desptr;
					break;

				default:
					break;
				}

				ctl_ctlblk.datacnt = ctl_ctlblk.dataptr[0];
				break;

			case USBD_DES_DEVICE_QUALIFIER_TYPE:
				ctl_ctlblk.dataptr = ctl_ctlblk.dev_qualifier_desptr;
				ctl_ctlblk.datacnt = ctl_ctlblk.dataptr[0];
				break;

			default:
				/* un-support descriptor here, return stall and back to previous function */
				drv_l1_usbd_send_ep0_stall();
				return;
			}

			//DBG_PRINT("USBDL2: USBD_STDREQ_GET_DESCRIPTOR, destype %d , len %d\r\n", destype,  ctl_ctlblk.datacnt);
			/* write and send descriptor data */
			_check_ep0_data_send_zero();
			cnt = drv_l1_usbd_send_ep0_in(ctl_ctlblk.dataptr, ctl_ctlblk.datacnt);
			ctl_ctlblk.datacnt -= cnt;
			ctl_ctlblk.dataptr += cnt;
		}
	}
	else
	if(bmtype == USBD_REQ_CLASS)
	{
		/* For Mass storage class, BOT transfer, send max logical number(max LUN) */
		switch(ctl_ctlblk.setupmsg.bRequest)
		{
		case USBD_REQ_GET_MAX_LUN_TYPE:
			{
				INT8U	maxlun;			// interfacenum;

				/* Get interface number from wIndex */
				//interfacenum = ctl_ctlblk.setupmsg.wIndex & 0xff;
				/* Check if correct parameters in setup message */
				if((ctl_ctlblk.setupmsg.wIndex & 0xFF) != ctl_ctlblk.cur_interface_num || ctl_ctlblk.setupmsg.wValue != 0 || ctl_ctlblk.setupmsg.wLength != 1)
				{
					drv_l1_usbd_send_ep0_stall();
					break;
				}

				/* Get correct LUN number and return it to host */
				maxlun = drv_l2_usbd_msdc_get_max_lun();
				ctl_ctlblk.dataptr = (INT8U *) &maxlun;
				ctl_ctlblk.datacnt = 1;
				cnt = drv_l1_usbd_send_ep0_in(ctl_ctlblk.dataptr, ctl_ctlblk.datacnt);

				//DBG_PRINT("USBDL2: GET LUN, data %d, datacnt %d!\r\n", *(ctl_ctlblk.dataptr), ctl_ctlblk.datacnt);
				ctl_ctlblk.datacnt = 0;
			}

			break;

		case USBD_REQ_MASS_STORAGE_RESET_TYPE:
			/* Check if correct parameters in setup message */
			if((ctl_ctlblk.setupmsg.wIndex & 0xFF) != ctl_ctlblk.cur_interface_num || ctl_ctlblk.setupmsg.wValue != 0 || ctl_ctlblk.setupmsg.wLength != 0)
			{
				drv_l1_usbd_send_ep0_stall();
				break;
			}

			/* Disable BULK IN/OUT stall */
			drv_l1_usbd_set_ep_stall(MASK_USBD_UDLC_STL_CLREP1STL | MASK_USBD_UDLC_STL_CLREP2STL, 0);

			/* Enable receive bulk out */
			drv_l1_usbd_enable_rec_bulk_out_pkt();

			/* If no error found, send IN ACK back for status stage */
			drv_l1_usbd_send_ep0_null_pkt();
			ctl_ctlblk.datacnt = 0;
			break;

		default:
			break;
		}
	}
	else
	if(bmtype == USBD_REQ_VENDOR)
	{ }
	else
	{
		/* To do, USBD_REQ_RESERVED */
	}
}

/* Get SETUP packet */
void drv_l2_usbd_get_setup_token(void)
{
	gp_memset((INT8S *) &(ctl_ctlblk.setupmsg), 0, sizeof(USBD_SETUP_MSG));

	drv_l1_usbd_get_setup_pkt((void *) &(ctl_ctlblk.setupmsg));
	ctl_ctlblk.ep0_sendnull = 0;

    USBD_L2LOG("USBDL2: SETUP bmRequestType 0x%x, bRequest %d,wValue %d,wIndex %d,wLength %d!\r\n",
                ctl_ctlblk.setupmsg.bmRequestType, ctl_ctlblk.setupmsg.bRequest, ctl_ctlblk.setupmsg.wValue,
                ctl_ctlblk.setupmsg.wIndex, ctl_ctlblk.setupmsg.wLength);
}

INT8U drv_l2_usbd_check_ep0_state_stage(INT8U datadir)
{
	INT8U	bmdir;
	INT8U	ret = 0;

	bmdir = (ctl_ctlblk.setupmsg.bmRequestType & 0x80) >> 7;
	if((bmdir == USBD_DATA_DIR_D2H && (datadir == USBD_DATA_DIR_IN_HANDLE)) || (bmdir == USBD_DATA_DIR_H2D && (datadir == USBD_DATA_DIR_OUT_HANDLE)))
	{
		ret = 1;
	}

	return ret;
}

/*****************************************************
    L2 state handler functions
******************************************************/

/******************************************************
*       Control Transfer
*       1. drv_l2_usbd_state_ctl_ep0_setup()
*       2. drv_l2_usbd_state_ctl_ep0_in()
*       3. drv_l2_usbd_state_ctl_ep0_out()
*
*       Note. These are used for handling control transfer
*******************************************************/
void drv_l2_usbd_state_ctl_ep0_setup(INT32U event)
{
	/* In a control transfer, get SETUP 8 bytes data and process it */
	switch(event)
	{
	case USBD_SETUP_CMD_EVENT:
		drv_l2_usbd_get_setup_token();
		drv_l2_usbd_process_setup_request();
		if(usbl2_detect_cbk != NULL)
		{
			usbl2_detect_cbk();
		}
		break;

	default:
		break;
	}
}

void drv_l2_usbd_state_ctl_ep0_in(INT32U event)
{
	INT32U	cnt;
	drv_l1_usbd_disable_ep0();
	switch(event)
	{
	case USBD_EP0_IN_ACK_EVENT:
		if(ctl_ctlblk.datacnt)
		{
			USBD_L2LOG("USBDL2: drv_l2_usbd_ctl_ep0_in, %d remain to send!\r\n", ctl_ctlblk.datacnt);
			cnt = drv_l1_usbd_send_ep0_in(ctl_ctlblk.dataptr, ctl_ctlblk.datacnt);
			ctl_ctlblk.datacnt -= cnt;
			ctl_ctlblk.dataptr += cnt;
		}
		else
		if(ctl_ctlblk.datacnt == 0)
		{
			if(ctl_ctlblk.ep0_sendnull)
			{
				/* send null IN when length from host is multiple of device's*/
				drv_l1_usbd_send_ep0_in(NULL, 0);
				ctl_ctlblk.ep0_sendnull = 0;

				//DBG_PRINT("USBDL2: EP IN send null\r\n");
			}
			else
			{
				//DBG_PRINT("USBDL2: EP0 IN ACK, enter state status!!\r\n");
				/* Check if state stage */
				if(drv_l2_usbd_check_ep0_state_stage(USBD_DATA_DIR_IN_HANDLE))
				{
					ctl_ctlblk.dataptr = NULL;
					drv_l1_usbd_enable_receive_ep0_out();

					//DBG_PRINT("State stage, IN\r\n");
					return;
				}
			}
		}

		break;

	default:
		break;
	}
}

void drv_l2_usbd_state_ctl_ep0_out(INT32U event)
{
	drv_l1_usbd_disable_ep0();
	switch(event)
	{
	case USBD_EP0_OUT_READY_EVENT:
		//DBG_PRINT("USBD_EP_OUT_READY_EVENT\r\n");
		if(ctl_ctlblk.datacnt == 0)
		{
			if(drv_l2_usbd_check_ep0_state_stage(USBD_DATA_DIR_OUT_HANDLE))
			{
				ctl_ctlblk.dataptr = NULL;

				/* after getting OUT data ,send EP0 IN NULL to finish state stage */
				drv_l1_usbd_send_ep0_in(NULL, 0);

				//USBD_L2LOG(" State stage, OUT\r\n");
			}
		}

		break;

	default:
		break;
	}
}

/******************************************************
*       Miscellaneous
*       1. drv_l2_usbd_misc_handle()
*
*       Note. This is used for status control and report
*              from USB device or the other modules in system.
*
*******************************************************/
void drv_l2_usbd_misc_handle(INT32U event)
{
	switch(event)
	{
	case USBD_MISC_RESET_EVENT:
		DBG_PRINT("USBDL2: drv_l2_usbd_misc_handle, Received RST!\r\n");
		drv_l2_usbd_ctl_reset();
		break;

	case USBD_MISC_SET_CONF_EVENT:
		DBG_PRINT("USBDL2: drv_l2_usbd_misc_handle, Get set config!\r\n");

		/* open bulk out pipe and interrupt */
#if (USBD_BULKEP == BULK_EP12)
		drv_l1_usbd_enable_rec_bulk_out_pkt();
#elif (USBD_BULKEP == BULK_EP89)
		drv_l1_usbd_enable_rec_bulk89_out_pkt();
#endif
		drv_l2_usbd_msdc_reset();
		break;

	case USBD_MISC_SET_INTERFACE_EVENT:
		USBD_L2LOG("USBDL2: drv_l2_usbd_misc_handle, Received SET INTERFACE command!\r\n");
		break;

	case USBD_MISC_GET_SUSPEND_EVENT:
		//DBG_PRINT("USBDL2: drv_l2_usbd_misc_handle, Received suspend!\r\n");
		drv_l1_usbd_suspend();
		break;

	case USBD_MISC_GET_RESUM_EVENT:
		USBD_L2LOG("USBDL2: drv_l2_usbd_misc_handle, Received resume!\r\n");
		break;

	case USBD_MISC_MSDC_INSERT_EVENT:
		USBD_L2LOG("USBDL2: USBD_MISC_MSDC_INSERT_EVENT!\r\n");
		break;

	case USBD_MISC_CLR_EP0_STALL_EVENT:
		drv_l1_usbd_get_setup_pkt((void *) &(ctl_ctlblk.setupmsg));
		DBG_PRINT("USBDL2: CLR0 value 0x%x, index 0x%x\r\n", ctl_ctlblk.setupmsg.wValue, ctl_ctlblk.setupmsg.wIndex);
		break;

	case USBD_MISC_CLR_EPX_STALL_EVENT:
		//drv_l1_usbd_get_setup_pkt((void*)&(ctl_ctlblk.setupmsg));
		DBG_PRINT("USBDL2: CLRX stall for other EP\r\n");
		break;

	case USBD_MISC_SET_ADDRESS_EVENT:
		DBG_PRINT("Reveived Set address = 0x%x in %s\r\n", rUDC_ADDR & 0x7F, drv_l1_usbd_get_cur_speed()==USB_SPEED_11?"full speed":"high speed");
		break;

	default:
		break;
	}
}

/*****************************************************
    USBD L2 API functions
******************************************************/
void drv_l2_usbd_set_ctl_blk(USBD_CONTROL *ctl)
{
	memcpy(&ctl_ctlblk, ctl, sizeof(USBD_CONTROL));
}

void drv_l2_usbd_get_ctl_blk(USBD_CONTROL *ctl)
{
	memcpy(ctl, &ctl_ctlblk, sizeof(USBD_CONTROL));
}

void drv_l2_usbd_register_detect_cbk(void *cbk)
{
	usbl2_detect_cbk = (USBD_L2_DECTECT_CALLBACK_FUN) cbk;
}

//L2 register transfer handler
void drv_l2_usbd_register_state_handler(INT8U transfer, USBD_L2_STATE_FUN setup, USBD_L2_STATE_FUN in, USBD_L2_STATE_FUN out)
{
	switch(transfer)
	{
	case USBD_XFER_CONTROL:
		usbdl2_control_tbl[USBD_XFER_CONTROL_EP0_SETUP] = setup;
		usbdl2_control_tbl[USBD_XFER_CONTROL_EP0_IN] = in;
		usbdl2_control_tbl[USBD_XFER_CONTROL_EP0_OUT] = out;
		break;

	case USBD_XFER_BULK:
		usbdl2_bulk_tbl[USBD_XFER_BULK_IN] = in;
		usbdl2_bulk_tbl[USBD_XFER_BULK_OUT] = out;
		break;

	case USBD_XFER_ISO:
		usbdl2_iso_tbl[USBD_XFER_ISO_IN] = in;
		usbdl2_iso_tbl[USBD_XFER_ISO_OUT] = out;
		break;

	case USBD_XFER_INT:
		usbdl2_intr_tbl[USBD_XFER_INT_IN] = in;
		usbdl2_intr_tbl[USBD_XFER_INT_OUT] = out;
		break;

	case USBD_XFER_MISC:
		usbdl2_misc_tbl[0] = setup; //miscellaneous
		break;

	default:
		break;
	}
}

//Register the descriptor which customer specifies
void drv_l2_usbd_register_descriptor(INT8U type, INT8U *desptr)
{
	switch(type)
	{
	case REG_DEVICE_DESCRIPTOR_TYPE:
		ctl_ctlblk.dev_desptr = desptr;
		break;

	case REG_CONFIG_DESCRIPTOR_TYPE:
		ctl_ctlblk.config_desptr = desptr;
		break;

	case REG_DEVICE_QUALIFIER_DESCRIPTOR_TYPE:
		ctl_ctlblk.dev_qualifier_desptr = desptr;
		break;

	case REG_REPORT_DESCRIPTOR_TYPE:
		ctl_ctlblk.report_desptr = desptr;
		break;

	case REG_STRING0_DESCRIPTOR_TYPE:
		ctl_ctlblk.str0_desptr = desptr;
		break;

	case REG_STRING1_DESCRIPTOR_TYPE:
		ctl_ctlblk.str1_desptr = desptr;
		break;

	case REG_STRING2_DESCRIPTOR_TYPE:
		ctl_ctlblk.str2_desptr = desptr;
		break;

	case REG_STRING3_DESCRIPTOR_TYPE:
		ctl_ctlblk.str3_desptr = desptr;
		break;

	case REG_STRING4_DESCRIPTOR_TYPE:
		ctl_ctlblk.str4_desptr = desptr;
		break;

	case REG_STRING5_DESCRIPTOR_TYPE:
		ctl_ctlblk.str5_desptr = desptr;
		break;

	default:
		break;
	}
}

void drv_l2_usbd_set_interface_num(INT8U num)
{
	ctl_ctlblk.cur_interface_num = num;
}

INT32S drv_l2_usbd_ctl_init(INT8U prio)
{
#if (_OPERATING_SYSTEM == _OS_UCOS2)
	OS_TCB	task_data;
	INT8U	err;
#endif

	/* Init control state handler */
	drv_l2_usbd_register_state_handler(USBD_XFER_CONTROL, drv_l2_usbd_state_ctl_ep0_setup, drv_l2_usbd_state_ctl_ep0_in, drv_l2_usbd_state_ctl_ep0_out);

	/* reset the control block of control transfer */
	gp_memset((INT8S *) &ctl_ctlblk, 0, sizeof(ctl_ctlblk));

	/* Register default descriptor pointer */
	ctl_ctlblk.dev_desptr = Default_Device_Descriptor_TBL;
	ctl_ctlblk.config_desptr = Default_Config_Descriptor_TBL;
	ctl_ctlblk.dev_qualifier_desptr = Default_Qualifier_Descriptor_TBL;
	ctl_ctlblk.report_desptr = NULL;

	ctl_ctlblk.str0_desptr = Default_String0_Descriptor;
	ctl_ctlblk.str1_desptr = Default_String1_Descriptor;
	ctl_ctlblk.str2_desptr = Default_String2_Descriptor;

	/* default interface_num is 0 */
	ctl_ctlblk.cur_interface_num = 0;

	/* Init miscellaneous function handle*/
	drv_l2_usbd_register_state_handler(USBD_XFER_MISC, drv_l2_usbd_misc_handle, NULL, NULL);

#if (_OPERATING_SYSTEM == _OS_UCOS2)
	/* Create USBDMSDC message queue */
	if(!USBDL2Q)
	{
		USBDL2Q = OSQCreate(USBDL2Q_Stack, USBD_QUEUE_MAX_LEN);
		if(!USBDL2Q)
		{
			DBG_PRINT("USBDL2MSDC: Create USBDL2Q failed!\r\n");
			return STATUS_FAIL;
		}
		else
		{
			/* clean the message Q */
			OSQFlush(USBDL2Q);
		}
	}

	if(OS_NO_ERR != OSTaskQuery(prio, &task_data))
	{
		err = OSTaskCreate(drv_l2_usbd_main, (void *) 0, &USBDL2Stack[USBDL2StackSize - 1], prio);

		if(err != OS_NO_ERR)
		{
			DBG_PRINT("USBDL2MSDC: create drv_l2_usbd_main task failed!\r\n");
			return STATUS_FAIL;
		}
	}
#endif
	return STATUS_OK;
}

INT32S drv_l2_usbd_ctl_uninit(void)
{
	gp_memset((INT8S *) &ctl_ctlblk, 0, sizeof(ctl_ctlblk));

	return STATUS_OK;
}

void drv_l2_usbd_ctl_reset(void)
{
	/* reset l2 setup msg, ep0dataptr, ep0datacnt */
	gp_memset((INT8S *) &(ctl_ctlblk.setupmsg), 0, sizeof(USBD_SETUP_MSG));
	ctl_ctlblk.dataptr = NULL;
	ctl_ctlblk.datacnt = 0;
}

#if (_OPERATING_SYSTEM == _OS_UCOS2)
void drv_l2_usbd_main_task_exit(INT8U prio)
{
	INT8U	err;

	OSTaskSuspend(prio);
	OSTimeDly(10);
	OSTaskDelReq(prio);
	OSTaskDel(prio);
	OSQFlush(USBDL2Q);
	OSQDel(USBDL2Q, OS_DEL_ALWAYS, &err);
}

void drv_l2_usbd_main(void *param)
{
	INT8U	err;

	INT32U	msg;
	USBD_L2LOG("USBDL2: Enter usbd_l2main task!\r\n");
	usbdl2task = 1;
	while(usbdl2task)
	{
		msg = USBD_L2_MSG_NULL;
		{
			/* Receive message from queue */
			msg = (INT32U) OSQPend(USBDL2Q, 0, &err);

			if(msg != USBD_L2_MSG_NULL)
			{
				/* run state handle */
				drv_l2_usbd_l2_run_handle(msg);
			}
		}
	}	//while(usbdl2task)

	usbdl2task = 0xFF;
}

#endif
